home *** CD-ROM | disk | FTP | other *** search
- /*
- File: DTS_SCSI_Format.c
-
- Contains: SCSI-specific code for DTS' SCSI sample
-
-
-
-
- Unfortunately, no matter how long awaited, it's still not done. In fact, this
- isn't even a release- this is just an image of the code taken in the middle of
- development.
-
- THIS CODE DOES NOT WORK AS A WHOLE. MUCH OF IT IS BUGGY AND / OR INCOMPLETE.
- YOU WOULD HAVE TO BE ABSOLUTELY INSANE TO USE ANY OF THIS CODE IN YOUR
- PROJECT WITHOUT EXTENSIVE THOUGHT, DEBUGGING AND TESTING.
-
-
-
-
-
-
- Written by: Kent Sandvik
-
- Copyright: © 1991 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- 07/16/92 khs added read capacity calls
- 07/15/92 khs additional code mangling based on code review
- 04/24/92 ckd added code for PartitionCurrentDevice()
- 04/21/92 khs added changes from code review
- 03/14/92 BJS changed header, added to project
- 10/19/91 khs first version
- To Do:
- */
-
- #define __FILE_NUMBER__ 0x0002
-
- #include <PLStringFuncs.h>
- #include <Devices.h>
- #include <Types.h>
- #include <Memory.h>
- #include <Resources.h>
- #include <Files.h>
- #include <OSEvents.h>
- #include <DiskInit.h>
- #include <Errors.h>
- #include <String.h>
- #include <ToolUtils.h>
- #include <Strings.h>
-
- #include "DTS_SCSI_Application.h"
- #include "DTS_SCSI_Format.h"
- #include "DTS_SCSI_IO.h"
- #include "DTS_SCSI_Driver.h"
- #include "DTS_SCSI_Debug.h"
-
- // bits in dCtlFlags
- enum {
- dOpened = 5,
- dRAMBased,
- drvrActive
- };
-
- struct DriverHeader {
- short drvrFlags;
- short drvrDelay;
- short drvrEMask;
- short drvrMenu;
- short drvrOpen;
- short drvrPrime;
- short drvrCtl;
- short drvrStatus;
- short drvrClose;
- Str255 drvrName;
- };
- typedef struct DriverHeader DriverHeader;
-
- // We cast the driver handle to this "type" to allow us to call it
- // (to install the driver).
- typedef void (*InstallFunctionPtr)(void);
-
- /********************************************************************************************
- *
- * State variables private to the routines in this file.
- *
- ********************************************************************************************/
-
- // The current SCSI device (kInvalidDevice if we don't have a device yet)
- SCSIAddress gCurrentDevice = kInvalidDevice;
-
- // The strings we'll use in validating one of our drives
- // This is set up by a call to SetValidationInfo.
- Str255 gVendorString;
- Str255 gProductString;
- Str255 gRevisionString;
-
- /********************************************************************************************
- * To call the driver to install itself, we need to pass parameters in
- * a couple of oddball registers. If I wasn't so lazy, I'd create a
- * little assembly-language procedure to save off the important registers
- * & load 'em up with the right parameters, call the driver, then restore
- * the registers. It might look something like this:
- * 0000: 48E7 0500 MOVEM.L D5/D7,-(A7) ;save registers
- * 0004: 206F 0008 MOVEA.L $0008(A7),A0 ;get partition map ptr
- * 0008: 2E2F 000C MOVE.L $000C(A7),D7 ;get defaultData
- * 000C: 3A2F 0010 MOVE.W $0010(A7),D5 ;get SCSI address
- * 0010: 226F 0012 MOVEA.L $0012(A7),A1 ;get driver ptr
- * 0014: 4E91 JSR (A1) ;call the driver
- * 0016: 4CDF 00A0 MOVEM.L (A7)+,D5/D7 ;restore the registers
- * 001A: 4FEF 0014 LEA $0014(A7),A7 ;clean up the stack
- *
- * Since I'm so lazy, I've created an inline function to do it for me:
- ********************************************************************************************/
- pascal void CallDriver(Ptr driver, SCSIAddress device, unsigned long
- defaultData, Partition *partitionMap) = { 0x48E7, 0x0500, 0x206F,
- 0x0008, 0x2E2F, 0x000C, 0x3A2F, 0x0010, 0x226F, 0x0012, 0x4E91,
- 0x4CDF, 0x00A0, 0x4FEF, 0x0014 };
-
- /********************************************************************************************
- *
- * SCSI utility functions
- *
- * These functions act as the interface between the application's functionality and the
- * low-level SCSI operations. They do specific SCSI-oriented tasks like testing whether a
- * specific SCSI device is ready and ours, writing the empty partition map, etc.
- *
- ********************************************************************************************/
-
- //
- // We're in the process of validating a drive, and have retrieved its vendor info.
- // Compare this Pascal string with as many characters from the drive string - if they
- // all match, return 1, else return 0.
- int MatchStrings(Str255 testString, char *driveString) {
- char *ours = &testString[1]; // point at the first character of our string
- short matchLen = testString[0]; // use the length of our string as the count to match
- while ((*(ours++) == *(driveString++)) && --matchLen) ;
- return (matchLen == 0); // did it match?
- }
-
- //
- // Check to see if this SCSI address has a device that's ready;
- // if so, see if it's one of our devices. Returns 0 if so, or
- // the error.
- //
- OSErr CheckSCSIDevice(SCSIAddress device) {
- OSErr anErr = noErr;
-
- // We'll allow the device a few chances to get ready; this is helpful for
- // drives with "unit attention" turned on (though our device doesn't do
- // this).
-
- anErr = SCSITestUnitReady(device);
- if (anErr != noErr)
- return anErr;
-
- // The device is ready - see if it's ours by asking it for its inquiry record.
- // Because we don't know how big this device's inquiry response will be, we'll
- // issue this command twice: the first time, we'll only request enough to retrieve
- // the response size (5 bytes).
-
- return (SCSIDoInquiryCommand(device));
- }
-
-
- // See if the device is ready. Since TestUnitReady doesn't involve
- // transfer of data, we don't need to pass buffer information. Also,
- // since we know this command doesn't involve retrieving data from the
- // drive's media, we use a pretty short timeout (if it fails, we'll
- // retry anyway).
-
- OSErr SCSITestUnitReady(SCSIAddress device) {
- SCSICommandBlock testCmd; // the command block needed for Test Unit Ready
- OSErr anErr;
- short retries;
-
- StuffSCSICommandBlock(testCmd, SCSICmd_TestUnitReady, 0, 0, 0, 0, 0);
- retries = 3;
- do {
- // See if the device is ready. Since TestUnitReady doesn't involve
- // transfer of data, we don't need to pass buffer information. Also,
- // since we know this command doesn't involve retrieving data from the
- // drive's media, we use a pretty short timeout (if it fails, we'll
- // retry anyway).
- anErr = SCSIOp(testCmd, sizeof(SCSICommandBlock), device, kShortTimeout,
- kNoBuffer);
- } while ((anErr != noErr) && --retries);
-
- return anErr;
- }
-
- // Issue an INQUIRY SCSI command to the device, first find out the size of the
- // record sent back, and then fetch the whole record, test for errors as well!
-
- OSErr SCSIDoInquiryCommand(SCSIAddress device) {
- SCSICommandBlock inquiryCmd; // our Inquiry command block
- OSErr anErr;
-
- // Here's the structure that we get back from an INQUIRY command:
- #define kVendorIDSize 8
- #define kProductIDSize 16
- #define kRevisionSize 4
- struct { // see ANSI SCSI standard for more info about these fields
- unsigned char fDeviceType; // these two fieds indentifies the device currently
- unsigned char fDeviceQualifier; // corrected to the logical unit
- unsigned char fVersion; // this defines the version level (vendor + standard)
- unsigned char fResponseFormat; // defines the used data format (SCSI INQUIRY or something else)
- unsigned char fAdditionalLength; // length of bytes of the parameters
- unsigned char fVendorUse1; // vendor specific data
- unsigned short fReserved1;
- unsigned char fVendorID[kVendorIDSize]; // vendor ID data
- unsigned char fProductID[kProductIDSize]; // product data
- unsigned char fRevision[kRevisionSize]; // product version data
- unsigned char fVendorUse2[20]; // vendor specific data
- unsigned char fReserved2[42];
- unsigned char fVendorUse3[158]; // vendor specific data
- } inquiryResponse;
- // We don't need to check the string size, because the inquiryResponse block can't be bigger
- // than 256 bytes.
-
- // We have to get at least this much to allow us to compare vendor strings!
- short minimumAdditionalLength = (short) (inquiryResponse.fProductID - &inquiryResponse);
-
- StuffSCSICommandBlock(inquiryCmd, SCSICmd_Inquiry, 0, 0, 0, 5, 0); // ask for 5 bytes
- anErr = SCSIOp(inquiryCmd, sizeof(SCSICommandBlock), device, kShortTimeout,
- &inquiryResponse, 5, kNoLoop, kDoRead, kDoPolled);
- // Skip out if we err'd, or if we didn't get enough data back.
- if ((anErr != noErr) || (inquiryResponse.fAdditionalLength < minimumAdditionalLength))
- return anErr;
-
- // Now that we know how big the response will be, reissue the command and ask for
- // the whole enchilada.
- StuffSCSICommandBlock(inquiryCmd, SCSICmd_Inquiry, 0, 0, 0,
- inquiryResponse.fAdditionalLength, 0); // ask for the whole thing
- anErr = SCSIOp(inquiryCmd, sizeof(SCSICommandBlock), device, kShortTimeout,
- &inquiryResponse, inquiryResponse.fAdditionalLength,
- kNoLoop, kDoRead, kDoPolled);
- if (anErr != noErr)
- return anErr;
-
- // We got the info. If all three strings match, it's good. Otherwise, it's not.
-
- return(SCSIMatchDriveInfo(inquiryResponse.fVendorID, inquiryResponse.fProductID,
- inquiryResponse.fRevision));
- }
-
-
- // Issue a READ CAPACITY command to the device in order to find out the
- // capacity (size) of the disk.
- // *** this is Undiscovered Code, boys and girls, please test and fix it ***
- unsigned long SCSIDoReadCapacityCommand(SCSIAddress device){
- SCSICommandBlock readcapCmd;
- unsigned long capacity;
- OSErr anErr;
-
- // here's the structure that we get back from a READ CAPACITY call
- unsigned long readcapResponse[2];
-
- // place together the SCSI command block
- StuffSCSICommandBlock(readcapCmd, SCSICmd_ReadCap, 0, 0, 0, 8, 0); // get back 8 bytes
-
- // call the SCSI device
- anErr = SCSIOp(readcapCmd, sizeof(SCSICommandBlock), device, kShortTimeout,
- &readcapResponse, sizeof(readcapResponse), kNoLoop, kDoRead, kDoPolled);
-
- assert(anErr == noErr, "Problems with READ CAPACITY SCSI call");
-
- // the value returned is the logical address of the last block, so
- // we add 1 to the total number of blocks times blocksize so
- // we will get the amount in bytes. Note that we get the physical block size
- // as well with the READ CAPACITY call.
-
- if (anErr == noErr)
- capacity = (readcapResponse[0] + 1) * readcapResponse[1];
- else
- capacity = 0; // Indicate error with 0 capacity
-
- return capacity;
- }
-
-
- // Get the volume name from the specified current SCSI device
- // *** Yet again, untested code, please test this out, Kent.
- Str31 SCSIGetVolumeName(){
- Str31 volumeName;
- HParamBlockRec hpb;
- OSErr anErr;
-
- hpb.volumeParam.ioNamePtr = volumeName; // we will get our name here
- hpb.volumeParam.ioVRefNum = ~(32 + gCurrentDevice); // refnum for the current SCSI device volume
- hpb.volumeParam.ioVolIndex = 0; // needs to be zero
-
- anErr = PBHGetVInfo(&hpb, false);
- assert(anErr == noErr, "Problems with PBHGetVInfo");
-
- return volumeName;
- }
-
-
- OSErr SCSIMatchDriveInfo(char* vendor, char* product, char* revision) {
- // We use global string constants in this case, because we want to have
- // the values locked to a particular drive. However it would make sense
- // to have these strings as resources instead for quick changes
-
- return (MatchStrings(gVendorString, vendor) &&
- MatchStrings(gProductString, product) &&
- MatchStrings(gRevisionString, revision)) ? noErr : -1;
- }
-
-
- //
- // Find the DCE for this SCSI device
- // Return NULL if the unit table slot was empty.
- //
- DCtlHandle DCEFromDevice(SCSIAddress device) {
- DCtlHandle *unitTable = *(DCtlHandle **) 0x11C;
- short unitEntry = (device + 32); // Unit table entries for SCSI start at 32
- return unitTable[unitEntry]; // Get the Device Control Entry for this device
- }
-
- //
- // Find the drive number for this SCSI device
- // Return 0 if none found.
- //
- short DriveFromDevice(SCSIAddress device) {
- short refnum;
- DrvQEl *dqe;
-
- // Convert the device address to a driver refnum,
- // and get the first drive in the queue
- refnum = ~(device + 32);
- dqe = (DrvQEl *) GetDrvQHdr()->qHead;
-
- // Walk the queue until we match our refnum
- // or run out of drives.
- while (dqe && (dqe->dQRefNum != refnum))
- dqe = (DrvQEl *) dqe->qLink;
-
- // Return 0 if we ran out, or the drive number we found.
- return dqe ? dqe->dQDrive : 0;
- }
-
- /********************************************************************************************
- *
- * High-level functions
- *
- * These functions are called by the application; they handle most of the user's commands:
- * switch to next drive, format current drive, test drive, etc. They also allow the application
- * level to find out about this level's state (like the ID of the current device, it's volume name
- * if any, etc).
- *
- ********************************************************************************************/
-
- //
- // Get the ID of the current device
- // *** eventually, get the volume name if it has one!
- //
- SCSIAddress CurrentDevice() {
- return gCurrentDevice;
- }
-
- //
- // Is this device run by no driver, someone else's driver, a current
- // version of our driver, or an old version of our driver? We
- // only allow the user to mess with this device if it's got no driver
- // or some version of our driver; we allow the user to update current
- // version of the driver only if it's the old version of our driver.
- //
- TDriverKind DriverKind(SCSIAddress device) {
- char *driverName,*driverVersion;
- DCtlHandle thisDCE;
-
- thisDCE = DCEFromDevice(device);
-
- // If we didn't find a device control entry for this device, there's no driver here.
- if (!thisDCE)
- return kNoDriver;
-
- // There's a driver here. If it's ours, it won't be marked "RAM-based"
- // (the flag really determines whether dCtlDriver is a handle or pointer;
- // for us, it's always a pointer).
-
- if ((*thisDCE)->dCtlFlags & (1 << dRAMBased))
- return kDifferentDriver;
-
- // The driver that's here isn't marked RAM-based, so it might be ours.
- // Check its name and version.
-
- driverName = ((DriverHeader*) (*(*thisDCE)->dCtlDriver))->drvrName;
-
- if ( PLstrcmp( kDriverName, driverName ) != 0)
- return kDifferentDriver;
-
- // The version is one byte immediately following the name
- // Advance the name pointer by the length of the name + 1 for the length byte
-
- driverVersion = driverName + (*driverName) + 1;
-
- if (*driverVersion == kCurrentDriverVersion)
- return kOurCurrentDriver;
- else
- return kOurOldDriver;
- }
-
-
- //
- // Get the volume reference number and name of the volume mounted on this
- // SCSI device (if any), in order to tell the user which volume will be
- // trashed in formatting. Assumes that we've already checked to see that
- // this device has some version of our driver controlling it (of course,
- // if it had no driver, it couldn't have a volume; likewise, if it had
- // some other driver, we couldn't format it anyway).
- //
- // For us, it's simple: we only support one volume per device: if we find
- // a volume that uses our refnum, we return its volume reference number
- // (were we a multiple logical-unit-number device, we'd have to investigate
- // further to make sure that the volume was really the one we planned on
- // messing with -- we'd have to pass a logical unit number to this routine as
- // well).
- //
- //
- // Return 0 if no volume mounted.
- //
- short VolumeFromDevice(SCSIAddress device, Str255 name) {
- DCtlHandle thisDCE;
- short anErr, index;
- HVolumeParam pb;
-
- // Find the device control entry that goes with this device.
- thisDCE = DCEFromDevice(device);
- if (!thisDCE)
- return 0; // No DCE, no volume, no shirt, no service.
-
- // Look at each volume control block until we find one that
- // uses our refnum, or run out of VCBs.
- index = 1; // start with first VCB
- do {
- pb.ioNamePtr = name; // Get the name
- pb.ioVolIndex = index++;
- anErr = PBHGetVInfo((HParmBlkPtr) &pb, false);
- } while ((anErr == 0) && (pb.ioVDRefNum != (*thisDCE)->dCtlRefNum));
-
- // If we fell out because of an error, assume that it was because
- // we ran out of VCBs.
- if (anErr != noErr) {
- name[0] = 0; // clear the name
- return 0;
- }
-
- // We succeeded in finding a refnum - return it.
- return pb.ioVRefNum;
- }
-
-
- //
- // Here's the vendor info we're s'posed to match to validate a drive
- //
- void SetValidationInfo(Str255 vendorString, Str255 productString, Str255 revisionString) {
- PLstrcpy(gVendorString, vendorString);
- PLstrcpy(gProductString, productString);
- PLstrcpy(gRevisionString, revisionString);
- }
-
- //
- // Find the next eligible device in the chain, and note its SCSI ID in gCurrentDevice.
- // Return 0 if we found a different one than the current one, or:
- // kNoOtherValidDrives: if the only drive we could find is the current one.
- //
- OSErr NextEligibleDevice() {
- short thisDeviceState;
- SCSIAddress sentinalDevice, thisDevice; // remember the previous current device
-
- // We're going to loop until we find a new good device, or until we get back to
- // where we started. If we're starting from having no valid device, we'll skip out when
- // after we've tried ID 6.
- sentinalDevice = (gCurrentDevice == kInvalidDevice) ? 6 : gCurrentDevice;
- thisDevice = sentinalDevice; // we'll start by checking the one after this one.
-
- // Loop through the chain until we find a good device, or until we hit our
- // sentinal device again.
- do {
- if (++thisDevice == 7)
- thisDevice = 0; // wrap around the SCSI chain
-
- thisDeviceState = CheckSCSIDevice(thisDevice); // try this device
- } while ((thisDeviceState != noErr) && (thisDevice != sentinalDevice));
-
- // We fell out of the loop, either because we got back to our original device,
- // or because we found another good one (Note that there's a possibility that
- // we got back to our original device and it was gone - that's why we test for
- // error first here).
- if (thisDeviceState == noErr) { // we found something good.
- if (thisDevice != gCurrentDevice) { // it's different that the last device!
- gCurrentDevice = thisDevice; // remember it
- return noErr; // return successfully.
- } else
- return kNoOtherValidDrives; // the only good one we found is the current one.
- } else
- return thisDeviceState; // there weren't any valid drives - pass on the last SCSI error.
- }
-
- //
- // Test the current SCSI device.
- // This routine assumes that the current device is one of our devices. The caller should
- // put up whatever message is appropriate ("Testing…") before calling this routine.
- //
- OSErr TestCurrentDevice() {
- return noErr;
- }
-
- //
- // Format the current SCSI device.
- // This routine assumes that the current device is one of our devices, and that it's OK with
- // the user that we trash it. The caller should put up whatever message is appropriate
- // ("Formatting…") before calling this routine.
- //
-
- OSErr FormatCurrentDevice() {
- return (FormatSCSIDevice(gCurrentDevice));
- }
-
-
- //
- // Format a particular SCSI device. This routine could be used to define a special
- // SCSI address and format the existing driver at this address. FormatCurrentDevice
- // is calling this lower level routine.
- //
-
- OSErr FormatSCSIDevice(SCSIAddress device) {
- unsigned char data;
- unsigned short interleave;
- SCSICommandBlock cmd;
- short anErr;
-
- // Some drives, like the Quantum drive we're formatting, require a specific data
- // pattern to be written during formatting. We set that here:
- data = 0x6C;
-
- // We also control the interleave pattern to be used. In our case we are
- // defining a hard coded value for Quantum drives. Set alternate values here:
- interleave = 1; //*** Are we going to change this?
-
- // Fill in the SCSI command block
- StuffSCSICommandBlock(cmd, SCSICmd_Format, 0, data, interleave >> 8, interleave, 0);
- //*** What are these 0's for? Pass constants.
- // Do the format
- anErr = SCSIOp(cmd, sizeof(SCSICommandBlock), device, kFormatTimeout,
- kNoBuffer);
-
- return anErr;
- }
-
- //
- // Load the driver into a nonrelocatable block in the system heap; we get it out
- // of a resource of type 'SCSI', name "DTS_SCSI" and copy it into a pointer block
- // in the system heap, returning the pointer. A nil return value signifies an
- // error.
- //
- OSErr
- LoadDriver(Ptr *driver,unsigned long *size)
- { Handle driverHandle;
- Ptr driverInstance;
- unsigned long driverSize;
-
- *driver = 0;
- *size = 0;
-
- // Load the driver resource
- driverHandle = Get1NamedResource('SCSI', "\pDTS_SCSI");
- assert(driverHandle,"no driver loaded!");
- if (!driverHandle)
- return ResError();
-
- driverSize = GetHandleSize(driverHandle);
-
- // The driver must be in a Ptr block in the system heap
- driverInstance = NewPtrSys(driverSize);
- assert(driverInstance,"unable to allocate driver storage");
- if (!driverInstance)
- return MemError();
-
- // Copy the driver to the pointer block
- BlockMove(*driverHandle,driverInstance,driverSize);
-
- ReleaseResource(driverHandle);
-
- *driver = driverInstance;
- *size = driverSize;
-
- return noErr;
- }
-
- //
- // Calculate the checksum using the algorithm described on IM V, pp 580-581
- //
- // This could be written to be significantly faster in assembly, or even by
- // using obscure tricks to fool the compiler into producing more optimal code,
- // but it's only used once for each format or update, and it's just not
- // worth it for the miniscule overall gain we would realize.
- //
-
- unsigned short
- FigureChecksum(Ptr data,unsigned long length)
- { register unsigned char *dataPtr;
- register unsigned long remaining;
- register unsigned short sum;
-
- remaining = length;
- dataPtr = data;
- sum = 0;
-
- // Loop for each of the characters, adding it in and rotating the checksum
- while (remaining--)
- { sum += *(dataPtr++);
-
- // Rotate left one bit; we have to manually rotate the top bit
- // around to the bottom because C only has a shift operator
-
- if (sum & 0x8000)
- sum = (sum << 1) | 1;
- else
- sum = sum << 1;
- }
-
- if (sum == 0)
- sum = 0xFFFF;
-
- return sum;
- }
-
- //
- // Fill in a partition map entry.
- //
- // This only fills in those fields that are used in most partitions and
- // assumes that its storage has been pre-initialized to zero. (We don't
- // fill in fields which would always be 0.)
- //
-
- void
- InitPartitionMapEntry(Partition *p, unsigned long startBlock, unsigned long partLength,
- char *partName, char *partType, unsigned short partStatus)
- {
- p->pmSig = 0x504D; // Signature is always $504D ('PM')
- p->pmMapBlkCnt = kPartitionCount; // Total number of partition map blocks
- p->pmPyPartStart = startBlock; // The first block in the partition
- p->pmPartBlkCnt = partLength; // Number of blocks in the partition
-
- assert(strlen(partName) <= 32,"Partition name is too long");
- assert(strlen(partType) <= 32,"Partition type is too long");
-
- strncpy(p->pmPartName,partName,32); // Copy 32 characters at most (handles
- strncpy(p->pmParType,partType,32); // not adding a nil if length == 32)
-
- p->pmDataCnt = partLength; // Number of blocks devoted to data
- p->pmPartStatus = partStatus; // Status information
- }
-
- //
- // This function is a catch-all for doing all the tasks to set up a disk:
- // 1) Setting up & writing out the disk structures, including the partition maps
- // 2) Writing out the driver into the driver partition
- // 3) Loading the now formatted drive's driver
- // 4) Calling DIZero to put the HFS structures onto the partition
- //
- // *** Think about splitting this up into more manageable chunks. It's HUGE!
- // (although, in all modesty, HDSetup is worse).
- //
-
- #define kPhysicalBlockSize 512
-
- OSErr
- PartitionCurrentDevice()
- { OSErr err;
- Block0 *ddm; // The image of our driver descriptor map
- Partition *partitionMap; // Our partition map
- Ptr driver; // The driver instance
- Ptr driverPartition; // Storage for constructing the driver partition
- unsigned long deviceBlocks; // The size of our device, in blocks
- unsigned long driverSize; // The size of our driver, in bytes
- unsigned long driverBlocks; // The size of our driver, in bytes
- unsigned short drvrChecksum; // Our driver's checksum
- unsigned long blockCount; // How many blocks for SCSIPrime to write
- short driveNum; // The drive number for our now formatted device
- unsigned char volName[32]; // The name to give to the new volume
-
- // Because the driver's size is needed to set up the driver descriptor map,
- // we'll start by loading it and figuring its checksum.
-
- err = LoadDriver(&driver,&driverSize);
- if (err || !driver)
- { if (err)
- return err;
- else
- return resNotFound; // Sometimes the resource manager punts on errors
- }
-
- driverBlocks = (driverSize + kPhysicalBlockSize - 1) / kPhysicalBlockSize;
- assert(driverBlocks <= kDriverSpace,"kDriverSpace isn't enough room for the driver");
-
- drvrChecksum = FigureChecksum(driver,driverSize);
-
- // Next, we're going to need to figure out our geometry. The following
- // things are given:
- // 1 block for the driver descriptor map in block 0.
- // kPartitionCount blocks for the partition map entries (currently 3):
- // 1 for the partition map itself
- // 1 for the driver partition
- // 1 for the actual HFS partition
- // kDriverSpace blocks for the driver partition (currently 20)
- //
- // the rest can be given to the HFS partition. Thus, the usable space for
- // the HFS partition is equal to:
- //
- // deviceSize - 1 - kPartitionCount - kDriverSpace;
- //
- // Because kDriverSpace is currently 20 blocks (10 K), this means we're
- // reserving 24 blocks (12 K) for our nefarious purposes.
- //
- // kDriverSpace is larger than it needs to be (the driver is only 3422 bytes
- // as of this writing), but reserving a few extra K is worth it for future
- // expansion, given the cost (7 K is not a lot of disk space) against the
- // risk (having to reformat the disk to grow the driver partition at some
- // point in the future because we didn't reserve enough space). It's my
- // guess that 10 K could accommodate a substantially more complex driver
- // than I plan on writing at any point in the forseeable future, so this
- // should do just fine.
- //
- // This also implies that the partition map starts at block #1 (zero-based),
- // the driver starts at block #(1+kPartitionCount) and the HFS partition
- // starts at block #(1+kPartitionCount+kDriverSpace).
-
- deviceBlocks = SCSIDoReadCapacityCommand(gCurrentDevice);
-
- if (deviceBlocks == 0) // ReadCapacity indicates errors with a 0.
- { DisposePtr(driver);
- return ioErr;
- }
-
- // First we fill in the driver descriptor map, or "block 0". This is
- // described in Inside Mac V, page 577. We've only got one driver
- // (thank God, one is more than enough), so we can use the basic
- // Block0 structure from SCSI.h.
-
- // The ddm occupies exactly one block, so that's what we allocate
- ddm = (Block0*)NewPtrClear(kPhysicalBlockSize);
-
- assert(ddm,"Couldn't allocate room for the ddm");
- if (!ddm)
- { err = MemError();
- DisposePtr(driver); // Release the driver's storage
- return err;
- }
-
- ddm->sbSig = 0x4552; // always $4552 ('EQ')
- ddm->sbBlkSize = kPhysicalBlockSize; // the block size of the device
- ddm->sbBlkCount = deviceBlocks; // the number of blocks on the device
- ddm->sbDevType = 1; // used internally
- ddm->sbDevId = 1; // used internally
- ddm->sbData = 0; // used internally
- ddm->sbDrvrCount = 1; // number of driver descriptors
- ddm->ddBlock = 1 + kPartitionCount; // the first block of the SCSI driver (right after the p-map)
- ddm->ddSize = driverBlocks; // The size of the driver, rounded up to the next block
- ddm->ddType = 1; // the System type (1 means Macintosh)
-
- // Now we write out the header block. I thought about holding off on doing any
- // writing until the entire set of information had been accumulated, but
- // decided the minor inconvenience of occasionally having a partially-formatted
- // disk wasn't enough to outweigh the convenience of having this function be
- // well-partitioned into individual steps.
-
- // Write 1 block of data taken from ddm to the current device, unit #0,
- // starting at block #0, using the standard block size, using a blind
- // transfer, with our standard I/O length timeout.
-
- blockCount = 1;
- err = SCSIPrime(ddm, CurrentDevice(), 0, 0, &blockCount, kPhysicalBlockSize,
- kDoWrite, kDoBlind, kIOTimeout);
-
- assert(!err,"Couldn't write out block 0");
-
- DisposePtr((Ptr)ddm); // Whether the write worked or not, we're done with this
-
- if (err)
- { DisposePtr(driver);
- return err;
- }
-
- // Now we're going to set up the partition map. We're only going to
- // allocate & use 3 partition map entries because we don't support multiple
- // partitions of any kind; if we did, we'd probably allocate a few extra
- // to give us room to repartition later.
- //
- // The three entries are, in turn, the entry for the map itself, then for the
- // driver, then for the HFS partition. (The order isn't important.)
-
- // Because we're making an array of several Partition structures, and we're
- // then going to write them all out at once, it's important that they each be
- // exactly the right length so they'll line up with the block boundaries
- // correctly. We can do this safely because the Partition structue has padding
- // at the end to make it exactly 512 bytes long. Were this not true, we would
- // have to allocate and refer to our array differently.
-
- assert(sizeof(Partition) == kPhysicalBlockSize,"Partition size is wrong for use in array");
-
- partitionMap = (Partition*)NewPtrClear(kPartitionCount * kPhysicalBlockSize);
-
- assert(partitionMap,"Couldn't allocate room for the partition map");
-
- if (!partitionMap)
- { err = MemError();
- DisposPtr(driver);
- return err;
- }
-
- // Fill in the entry for the partition map itself
- // It starts at block 1 and goes for kPartitionCount blocks,
- // and has the names as described on page 580 of Inside Mac V.
- // Its status byte is binary 00010011 or hex $13
- // (See IM V-581 for a description of all the bit flags; this has
- // only allowsReading + isAllocated + isValid set)
-
- // *** Remember to check on correctness / necessity of status flags.
-
- InitPartitionMapEntry(&partitionMap[0],1,kPartitionCount,
- "Apple","Apple_partition_count",0x13);
-
- // Now the entry for the driver
- // It starts at block 1 + kPartitionCount and goes for kDriverSpace blocks.
- // Its status is binary 01011011 or hex $5B; relocatableCode +
- // allowsReading + hasBootInfo + isAllocated + isValid.
-
- InitPartitionMapEntry(&partitionMap[1],1+kPartitionCount,kDriverSpace,
- "MacOS","Apple_Driver",0x5B);
-
- // In addition to the common stuff done by InitPartitionMapEntry,
- // the entry for the device driver needs some more info:
-
- partitionMap[1].pmBootSize = driverSize; // The actual size of the driver code
- partitionMap[1].pmBootCksum = drvrChecksum; // The checksum of the driver
- strcpy(partitionMap[1].pmProcessor,"68000"); // The target processor
-
- // Now the entry for the HFS partition
- // It starts at block 1 + kPartitionCount + kDriverSpace and goes for
- // deviceSize - 1 - kPartitionCount - kDriverSpace blocks.
- // Its status is binary 00110011 or hex $33; allowsWriting + allowsReading +
- // isAllocated + isValid.
-
- InitPartitionMapEntry(&partitionMap[2],1+kPartitionCount+kDriverSpace,
- deviceBlocks-1-kPartitionCount-kDriverSpace,
- "MacOS","Apple_HFS",0x33);
-
- // Now we'll write out the partition map
-
- // Write out kPartitionCount blocks starting at block 1 from the data at
- // address partitionMap to the current SCSI device, unit #0, using a blind
- // transfer, with the standard I/O timeout
-
- blockCount = kPartitionCount;
- err = SCSIPrime(partitionMap, CurrentDevice(), 0, 1, &blockCount,
- kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
-
- assert(!err,"Couldn't write out the partition map");
-
- // I would normally dispose of the partition maps here, but we'll need them
- // to supply to the driver, down below, so I've got to save them
-
- if (err)
- { DisposePtr(driver);
- DisposePtr((Ptr)partitionMap);
- return err;
- }
-
- // Now we'll write out the driver. For purist reasons, I'm going to fill up
- // the extra room in the partition with zeros. This isn't necessary, but
- // fills a deep-seated Freudian need within me. Because we can only write
- // out data in block increments, I can't write out the driver, then the
- // zeros in two steps, because it's unlikely that the driver will fall exactly
- // on a block boundary. Theoretically, I could do it all with just a one
- // block size buffer to deal with the boundary and to write the zeros out of,
- // but the entire driver partition is only 10K, so I'll just take the cheap way
- // out and construct the whole thing, then write it out.
-
- // Allocate using NewPtrClear to guarantee padding zeros
- driverPartition = NewPtrClear(kDriverSpace * kPhysicalBlockSize);
-
- if (!driverPartition)
- { err = MemError();
- DisposePtr(driver);
- DisposePtr((Ptr)partitionMap);
- return err;
- }
-
- // Copy the driver into its partition room
- BlockMove(driver,driverPartition,driverSize);
-
- // Now we write it out
-
- // Write out kDriverSpace blocks starting at block 1+kPartitionCount with
- // all the usual stuff
-
- blockCount = kDriverSpace;
- err = SCSIPrime(driverPartition, CurrentDevice(), 0, 1+kPartitionCount,
- &blockCount, kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
-
- assert(!err,"Couldn't write out driver partition");
-
- DisposePtr(driverPartition); // Regardless, we're done with this now
-
- if (err)
- { DisposePtr(driver);
- DisposePtr((Ptr)partitionMap);
- return err;
- }
-
- // OK, the drive should now be fully set up; all that remains is to get
- // the driver to install itself and zero out the HFS stuctures with DIZero
-
- // First, we'll call the driver so it can install itself; it needs to know
- // which SCSI device it is in charge of, its default data, and a pointer to
- // the partition map for the HFS partition.
-
- CallDriver(driver, CurrentDevice(), 0, &partitionMap[2]);
-
- DisposePtr((Ptr)partitionMap); // Now we're done with this
-
- // We check to see if it has successfully done its thing by seeing if it
- // has installed a drive in the drive queue
-
- driveNum = DriveFromDevice(CurrentDevice());
-
- assert(driveNum != 0,"Driver didn't install a drive");
-
- if (driveNum == 0) // 0 means no drive means an error means uh-oh
- { DisposePtr(driver);
- return ioErr; // to pick an error at random *** Better Error?
- }
-
- // Now the driver is irretrievably linked into the OS, so we're gonna let it lie
-
- // Now that we're this far along, the volume is ready to recieve the HFS
- // formatting (a blank directory, setting up the catalog file, etc.)
-
- // Get the default volume name from the 'STR#' resource
- GetIndString(volName,kStringList,kVolumeNameString);
-
- // Create all the HFS info
- err = DIZero(driveNum,volName);
-
- assert(!err,"DIZero returned an error");
-
- return err;
- }
-
- //
- // FindPartitionEntry
- // Finds a partition that matches a given name and type and returns its entry and
- // its index into the partition map entry (physical block # = entry+1)
- // If name or type is nil, it is ignored; any name or type will match. Otherwise,
- // pass C-style strings (null-terminated) and they'll be compared case-insensitively
- //
-
- OSErr
- FindPartitionEntry(Partition *p,unsigned long *block,unsigned char *name,unsigned char *type)
- { short entry;
- unsigned long blockCount;
- char string[33];
- Boolean equal;
- OSErr err;
-
- assert(sizeof(*p) == kPhysicalBlockSize,"Partition struct is not the same size as a disk block");
-
- entry = 0;
-
- do
- { blockCount = 1;
- err = SCSIPrime(p,CurrentDevice(), 0, entry+1, &blockCount, kPhysicalBlockSize,
- kDoRead, kDoBlind, kIOTimeout);
-
- assert(err == noErr,"Couldn't read partition map entry");
- if (err)
- return err;
-
- if (name == 0)
- equal = true; // A nil name pointer matches anything
- else
- { // I have to go through various contortions because the name & type may be
- // up to 32 characters long, and if they're exactly that long, they're not
- // null terminated, so I can't use standard string manipulation routines
- // I want to use EqualString to do a case-insensitive string compare, so
- // I have to convert both arguments to pascal strings.
-
- BlockMove(p->pmPartName,string,32); // Copy the 32 bytes of the name
- string[32] = '\0'; // Null-terminate if it's exactly 32 bytes long
- c2pstr(string); // Convert it to pascal format
- c2pstr(name); // Convert the name argument
-
- // Do a case-insensitive compare
- equal = EqualString(string,name,false,true);
-
- p2cstr(name); // Convert the name back
- }
-
- // If we got a match, try the same thing for the type
- if (equal)
- { if (type == 0)
- equal = true; // A nil type pointer matches anything
- else
- { BlockMove(p->pmParType,string,32); // Copy the 32 bytes of the name
- string[32] = '\0'; // Null-terminate if it's exactly 32 bytes long
- c2pstr(string); // Convert it to pascal format
- c2pstr(type); // Convert the name argument
-
- // Do a case-insensitive compare
- equal = EqualString(string,type,false,true);
-
- p2cstr(type); // Convert the name back
- }
-
- // If they both match, fill in the block and return;
- // the partition struct is already filled in
- if (equal)
- { *block = entry + 1;
- return noErr;
- }
- }
-
- entry++; // Advance to the next partition map entry
- } while (entry < p->pmMapBlkCnt); // Until we've done all the entries
-
- assert(false,"Couldn't find a matching partition entry");
-
- // If we get here, we didn't find a matching entry; return an error
- // *** I should select a more appropriate error
-
- return ioErr;
- }
-
- //
- // Update the current device with the newest driver, then remount the volume
- //
- // This requires reading the driver descriptor map and updating it,
- // finding the driver partition, making sure it's large enough and
- // writing out the new driver, updating the driver descriptor,
- // then finding the HFS partition and remounting the device.
- //
- // *** I should probably make sure the drive in question is being handled by
- // our driver.
- //
-
- OSErr
- UpdateCurrentDevice()
- { OSErr err;
- unsigned long blockCount; // Transfer length for use by SCSIPrime
- Block0 ddm; // The driver descriptor map for the device
- Partition pme; // A partition map entry
- unsigned long pmeBlock; // Block number of the partition map entry
- unsigned long drvrSize; // driver size
- unsigned short drvrBlockSize; // driver size in blocks
- unsigned short drvrChksum; // driver checksum
- Ptr driver; // pointer to loaded driver
- unsigned long drvrPartSize; // size of the driver's partition
- Ptr driverOut; // pointer to disk image of driver partition
- short driveNum; // Driver's new drive number
-
- // Make sure ddm and pme are the right size to hold one disk block
- assert(sizeof(ddm) == kPhysicalBlockSize,"Block0 struct ddm is not the same size as a disk block");
-
- // Read in block 0
- // Read one block into ddm starrting from block 0
- blockCount = 1;
- err = SCSIPrime(&ddm, CurrentDevice(), 0, 0, &blockCount, kPhysicalBlockSize,
- kDoRead, kDoBlind, kIOTimeout);
-
- assert(err == noErr,"Failed to read block 0 from drive");
-
- if (err)
- return err;
-
- // Now we're going to go looking for the driver's partition entry
- // Find a partition entry of name "MacOS" and type "Apple_Driver"
- // on the current device, starting with the first entry
-
- err = FindPartitionEntry(&pme,&pmeBlock,"MacOS","Apple_Driver");
-
- assert(err == noErr,"Couldn't find driver partition");
-
- if (err)
- return err;
-
- // If we've gotten this far, we should go and get a copy of the driver
- // we'll be installing on the disk and get its checksum.
-
- err = LoadDriver(&driver,&drvrSize);
- assert(err == noErr,"Couldn't load driver image");
- if (err)
- return err;
-
- drvrChksum = FigureChecksum(driver,drvrSize);
- drvrBlockSize = (drvrSize + kPhysicalBlockSize - 1) / kPhysicalBlockSize;
-
- // Now we make sure that the partition is large enough to hold the new
- // driver
-
- drvrPartSize = pme.pmPartBlkCnt * kPhysicalBlockSize;
-
- assert(drvrSize <= drvrPartSize,"Partition isn't big enough for driver");
-
- if (drvrSize > drvrPartSize)
- { DisposePtr(driver);
- return kPartitionTooSmall;
- }
-
- // Because I'm picky, I like to fill up the extra space in the driver
- // partition with zeros, rather than leave garbage trailing it. Because
- // the driver image I've loaded is in a fitted block in the system heap,
- // and we can only write 512-byte blocks, I've got to copy the driver
- // into another block to do this. If this block can't be allocated
- // (for example, because the disk has a ten megabyte driver partition
- // and I can't allocate that much RAM), then I just bite the bullet and
- // write the mere minimum directly out of the system heap. This means
- // that we will be leaving garbage on the disk; not only do I not erase
- // the previous driver completely, but I will be writing off of the end
- // of the driver in the system heap, causing some bits of other stuff to
- // be written with the driver. This means we could have problems if
- // for some reason this area after our loaded driver (up to 511 bytes)
- // could not be accessed; if, for example, we were at the very upper
- // boundary of RAM or if some segments of the system heap were made
- // inaccessible by a future version of the OS. I'm going to take
- // that risk. ??? Maybe I shouldn't?
-
- driverOut = NewPtrClear(drvrPartSize);
-
- assert(driverOut != 0,"Couldn't allocate partition write buffer");
-
- // If our allocation succeeds, copy the driver over
- if (driverOut)
- BlockMove(driver,driverOut,drvrSize);
-
- // Now we'll update our local copies of the driver descriptor map
- // and the partition map entry to reflect our new driver. Very
- // few fields need to be changed; only those describing the actual
- // driver code, since the partition information remains the same.
-
- // First, update the ddm
- ddm.ddSize = drvrBlockSize; // Size of the driver code in blocks
- ddm.ddType = 1; // The system type (1 for Mac)
-
- // Now, this partition's map entry
- pme.pmBootSize = drvrSize; // Driver's size, in bytes
- pme.pmBootCksum = drvrChksum; // Driver's checksum
- pme.pmBootAddr = 0; // Both of these fields should
- pme.pmBootEntry = 0; // already be 0, just making sure
- strcpy(pme.pmProcessor,"68000"); // That good ol' 68K
-
- // Here begins the critical portion of the code; once we begin this
- // write, the disk is damaged until we do our last write. Should
- // the power fail or the computer be crushed by a herd of stampeding
- // wildebeest while all this is going on, bad things could happen.
- // To minimize the risk, I write out the info in a particular order:
- // first, the driver itself; should this fail, at worst, the disk
- // will not boot (or will crash while trying to boot) and will need
- // to be re-updated. Then the partition; while it is unlikely, should
- // the update fail in this step, damage to the entire disk is
- // possible, perhaps requiring reformatting. Finally, the driver
- // descriptor map; this is likely to do the most damage if it fails
- // in an interesting way. By moving the most-critical bits to the
- // end, I hope to minimize the chance of writing incorrect information
- // into these areas.
-
- // If I've succeeded in allocating my own block for the partition, then
- // write it out; otherwise, write the driver straight out of the system
- // heap.
-
- if (driverOut)
- { blockCount = pme.pmPartBlkCnt;
- err = SCSIPrime(driverOut, CurrentDevice(), 0, pme.pmPyPartStart, &blockCount,
- kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
- DisposePtr(driverOut); // regardless of success in writing,
- // we're done with this block
- }
- else
- { blockCount = drvrBlockSize; // just write out the driver code alone
- err = SCSIPrime(driver, CurrentDevice(), 0, pme.pmPyPartStart, &blockCount,
- kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
- }
-
- assert(err == noErr,"SCSIPrime failed to write driver");
-
- if (err)
- { DisposePtr(driver);
- return err;
- }
-
- // Next, the partition map entry
- blockCount = 1;
- err = SCSIPrime(&pme, CurrentDevice(), 0, pmeBlock, &blockCount,
- kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
-
- assert(err == noErr,"SCSIPrime failed to write partition map entry");
-
- if (err)
- { DisposePtr(driver);
- return err;
- }
-
- // Now, block 0
- blockCount = 1;
- err = SCSIPrime(&pme, CurrentDevice(), 0, 0, &blockCount,
- kPhysicalBlockSize, kDoWrite, kDoBlind, kIOTimeout);
-
- assert(err == noErr,"SCSIPrime failed to write driver descriptor map");
-
- if (err)
- { DisposePtr(driver);
- return err;
- }
-
- // OK, now everything's written out to the disk; all that remains is
- // to find the HFS partition and call the driver so it will mount itself.
-
- err = FindPartitionEntry(&pme,&pmeBlock,"MacOS","Apple_HFS");
-
- assert(err == noErr,"Couldn't find the HFS partition");
-
- if (err)
- { DisposePtr(driver);
- return err;
- }
-
- // Call the driver; it takes care of installing itself and causing
- // its device to be mounted.
-
- CallDriver(driver, CurrentDevice(), 0, &pme);
-
- // To see if it worked, make sure it created a drive queue entry
-
- driveNum = DriveFromDevice(CurrentDevice());
-
- assert(driveNum != 0,"Driver didn't install a drive");
-
- if (driveNum == 0) // 0 means no drive means an error means uh-oh
- { DisposePtr(driver);
- return ioErr; // to pick an error at random *** Better Error?
- }
-
- return noErr; // All was successful
- }
-
- //
- // Mount the newly formatted and partitioned device.
- // The driver's already been installed and opened, so everything
- // is ready to insert the disk.
- //
- OSErr MountCurrentDevice() {
- DCtlHandle dce;
- TDriveVars *vars;
- short driveNum;
-
- dce = DCEFromDevice(gCurrentDevice); //*** Is this being changed to use driveFromDevice?
- assert(dce,"DCE is nil at drive mounting time");
- if (!dce)
- return nsDrvErr;
-
- vars = *(TDriveVars **) (**dce).dCtlStorage;
- assert(vars,"DCtlStorage is nil at drive mounting time");
- if (!vars)
- return nsDrvErr;
-
- driveNum = vars->driveQElem.elem.dQDrive;
-
- PostEvent(diskEvt,driveNum); //*** Comment this (just in case).
-
- return noErr;
- }
-
- //
- // Dispose of a driver. This is essentailly the same code as in ReplaceDriver()
- // in DTS_SCSI_Install.c.
- //
-
- static void DisposDriver(Ptr driver)
- { long offset;
- Ptr driverBlock;
-
- offset = *(((long*)driver) - 1); // Offset is one long back from driver
- driverBlock = ((Ptr)driver) - offset; // Back up to start of block
-
- // Now that we've got the start of the block, we need to deallocate the pointer
- // block. Theoretically, the driver could have been allocated in a locked
- // handle, but we're now requiring that anyone who loads arbitrary drivers
- // load them into a pointer block in the System heap. Thus, we assume that
- // we've now got a pointer to a Ptr block, and we'll just dispose of it.
-
- DisposPtr(driverBlock); // Dispose of old driver block
- }
-
- //
- // Unmount the volume on this partition, if we can.
- //
- OSErr UnmountCurrentDevice() {
- DCtlHandle dce,driveDCE;
- int volIndex;
- HVolumeParam volPB;
- short anErr;
- OSErr err;
- DrvQEl *drive;
-
- // Find the DCE for this SCSI device; it'll be NIL if there's
- // no open driver.
- dce = DCEFromDevice(gCurrentDevice);
- if (!dce)
- return noErr;
-
- // There's a driver here. Look through the volume queue until we
- // find a volume on this drive. If we don't find one, we're done.
- volIndex = 0;
- while (true) {
- // Get the next volume from the volume list
- volPB.ioVolIndex = ++volIndex;
- volPB.ioNamePtr = NULL;
- volPB.ioVDrvInfo = -1; // a wacky drive number
- anErr = PBHGetVInfo((HParmBlkPtr) &volPB, false);
-
- // If we ran out of volumes, it means there's no volume on this
- // device. We're done!
- if (anErr == nsvErr) // we ran out of volumes, so there's nothing to unmount
- return noErr;
-
- // If this volumes' driver refnum is ours, then this is our
- // volume.
- if ((anErr == noErr) && (volPB.ioVDRefNum == ~(32 + gCurrentDevice)))
- break;
- }
-
- // We found a volume on this device. Unmount it, if we can
- volPB.ioNamePtr = NULL;
- volPB.ioVRefNum = volPB.ioVDrvInfo; // copy the drive number
- if (err = PBUnmountVol((ParmBlkPtr) &volPB))
- return err;
-
- // Search the drive queue for another drive using this driver
- drive = (DrvQEl*)(GetDrvQHdr()->qHead);
-
- while (drive)
- { driveDCE = GetDCtlEntry(drive->dQRefNum);
- // If the drivers match, return with noErr (we'll be killing no drivers today)
- if ((**driveDCE).dCtlDriver == (**dce).dCtlDriver)
- return noErr;
- drive = (DrvQEl*)drive->qLink;
- }
-
- // No drivers matched, so we should dispose of this one.
- DisposDriver((**dce).dCtlDriver);
- }